﻿using StackExchange.Redis;

namespace _5_NovaAdmin.Infrastructure.Redis;
public class RedisService
{
    public readonly StackExchange.Redis.IDatabase _db;
    public readonly StackExchange.Redis.ISubscriber _subscriber;
    public RedisService(RedisClient client)
    {
        _db = client.GetDatabase();
        _subscriber = client.GetSubscriber();
    }

    #region Key 操作
    public async Task<bool> KeyExistsAsync(string key) => await _db.KeyExistsAsync(key);

    public async Task<bool> DeleteKeyAsync(string key) => await _db.KeyDeleteAsync(key);

    public async Task<long> DeleteKeysAsync(IEnumerable<string> keys) => await _db.KeyDeleteAsync(keys.Select(k => (RedisKey)k).ToArray());

    public async Task<bool> KeyRenameAsync(string key, string newKey) => await _db.KeyRenameAsync(key, newKey);

    public async Task<bool> SetExpirationAsync(string key, TimeSpan expiry) => await _db.KeyExpireAsync(key, expiry);
    #endregion

    #region String 操作
    public async Task<bool> StringSetAsync(string key, string value, TimeSpan? expiry = null) => await _db.StringSetAsync(key, value, expiry);

    public async Task<string> StringGetAsync(string key) => await _db.StringGetAsync(key);

    public async Task<long> StringAppendAsync(string key, string value) => await _db.StringAppendAsync(key, value);

    public async Task<bool> StringSetMultipleAsync(IDictionary<string, string> keyValues)
    {
        var pairs = keyValues.Select(kv => new KeyValuePair<RedisKey, RedisValue>(kv.Key, kv.Value)).ToArray();
        return await _db.StringSetAsync(pairs);
    }

    public async Task<IEnumerable<string>> StringGetMultipleAsync(IEnumerable<string> keys)
    {
        var redisKeys = keys.Select(k => (RedisKey)k).ToArray();
        return (await _db.StringGetAsync(redisKeys)).Select(v => (string)v);
    }
    #endregion

    #region Hash 操作
    public async Task<bool> HashSetFieldAsync(string key, string field, string value) => await _db.HashSetAsync(key, field, value);

    public async Task<string> HashGetFieldAsync(string key, string field) => await _db.HashGetAsync(key, field);

    public async Task<HashEntry[]> HashGetAllAsync(string key) => await _db.HashGetAllAsync(key);

    public async Task<bool> HashDeleteFieldAsync(string key, string field) => await _db.HashDeleteAsync(key, field);

    public async Task<long> HashDeleteFieldsAsync(string key, IEnumerable<string> fields) => await _db.HashDeleteAsync(key, fields.Select(f => (RedisValue)f).ToArray());

    public async Task HashSetObjectAsync<T>(string key, T obj) where T : class
    {
        var entries = ObjectToHashEntries(obj);
        await _db.HashSetAsync(key, entries);
    }

    public async Task<T> HashGetObjectAsync<T>(string key) where T : new()
    {
        var entries = await _db.HashGetAllAsync(key);
        return HashEntriesToObject<T>(entries);
    }
    #endregion

    #region List 操作
    public async Task<long> ListPushAsync<T>(string key, IEnumerable<T> items)
    {
        var values = items.Select(item => (RedisValue)JsonConvert.SerializeObject(item)).ToArray();
        return await _db.ListRightPushAsync(key, values);
    }

    public async Task<IEnumerable<T>> ListRangeAsync<T>(string key, long start = 0, long stop = -1)
    {
        var values = await _db.ListRangeAsync(key, start, stop);
        return values.Select(v => JsonConvert.DeserializeObject<T>(v));
    }

    public async Task<T> ListPopAsync<T>(string key)
    {
        var value = await _db.ListLeftPopAsync(key);
        return JsonConvert.DeserializeObject<T>(value);
    }
    #endregion

    #region 通用工具方法
    private HashEntry[] ObjectToHashEntries<T>(T obj) where T : class
    {
        return typeof(T).GetProperties()
            .Where(p => p.CanRead)
            .Select(p => new HashEntry(p.Name, p.GetValue(obj)?.ToString() ?? string.Empty))
            .ToArray();
    }

    private T HashEntriesToObject<T>(HashEntry[] entries) where T : new()
    {
        var obj = new T();
        var properties = typeof(T).GetProperties().Where(p => p.CanWrite);

        foreach (var prop in properties)
        {
            var entry = entries.FirstOrDefault(e => e.Name == prop.Name);
            if (entry.Value.HasValue)
            {
                prop.SetValue(obj, Convert.ChangeType(entry.Value, prop.PropertyType));
            }
        }
        return obj;
    }
    #endregion

    #region 分布式系统相关
    /// <summary>
    /// 设置一个分布式锁，如果锁定成功，就去执行业务
    /// </summary>
    /// <param name="lockKey">标识一个特定锁，多个客户端可以尝试获取同一个 lockKey，但只有其中一个客户端能够成功获取锁</param>
    /// <param name="lockValue">标识获取锁的客户端（随机值），确保只有锁的持有者才能释放锁，从而避免误删其他客户端的锁</param>
    /// <param name="expiry"></param>
    /// <returns></returns>
    public async Task<bool> AcquireLockAsync(string lockKey, string lockValue, TimeSpan expiry)
    {
        return await _db.LockTakeAsync(lockKey, lockValue, expiry);
    }

    /// <summary>
    /// 释放获取的分布式锁
    /// </summary>
    /// <param name="lockKey"></param>
    /// <param name="lockValue"></param>
    /// <returns></returns>
    public async Task ReleaseLockAsync(string lockKey, string lockValue)
    {
        await _db.LockReleaseAsync(lockKey, lockValue);
    }

    /// <summary>
    /// 将指定键的值递增
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public async Task<long> IncrementAsync(string key, long value = 1) => await _db.StringIncrementAsync(key, value);

    /// <summary>
    /// 将指定键的值递减
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public async Task<long> DecrementAsync(string key, long value = 1) => await _db.StringDecrementAsync(key, value);
    #endregion

    #region 发布订阅
    public void PublishMessage(string topticName, string message)
    {
        long publishLong = _subscriber.Publish(topticName, message);
        Console.WriteLine($"发布消息成功：{publishLong}");
    }

    public void SubScriper(string topticName, Action<RedisChannel, RedisValue> handler = null)
    {
        ChannelMessageQueue channelMessageQueue = _subscriber.Subscribe(topticName);
        channelMessageQueue.OnMessage(channelMessage =>
        {
            if (handler != null)
            {
                string redisChannel = channelMessage.Channel;
                string msg = channelMessage.Message;
                handler.Invoke(redisChannel, msg);
            }
            else
            {
                string msg = channelMessage.Message;
                Console.WriteLine($"订阅到消息: {msg},Channel={channelMessage.Channel}");
            }
        });
    }
    #endregion
}
